/**
  ETFDAQ Project
  \class TAmTDC32
  \brief a 32 ECL ch TDC Mesytec card with 4ps resolution
  \author SUN Yazhou, asia.rabbit@163.com
  \since 2024-09-05
  \date 2024-09-05 last modified

  \copyright Copyright (c) 2024 IMP-CAS with LGPLv3 LICENSE
*/

#include <iostream>
#include "TAmTDC32.h"
#include "TAMsg.h"
#include "TAYaml.h"
#include "mesy_reg.h"
#include "card_type.h"

using std::cout;
using std::endl;

#define er TAMsg::Error
#define wn TAMsg::Warn

// resolutions, in ns
const double TAmTDC32::kRes[10] = {
  0., 0., 1/256., 1/128., 1/64., 1/32., 1/16., 0.125, 0.25, 0.5
}; // in ns
const double TAmTDC32::kThreStep = 1000.*1.5/256.; // in mV

TAmTDC32::TAmTDC32(TAController *ctrl, const TAYaml &c) : TAMesytec(ctrl, c){
  fCardPurpose = ECardPurpose::TDC;
} // end ctor

TAmTDC32::~TAmTDC32(){}

// config the card
void TAmTDC32::Initialize(){
  TAMesytec::Initialize();
  if(fIRQLevel) ConfigIRQ(fIRQLevel, fIRQVector, fEventTrigger, true);

  // multi-event buffer
  // 0: single-event transfer; 1: unlimited multi-event transfer
  // 2: read max_transfer_data words, then emit berr
  // 0b1011 (0xb): max_transfer_data is in events
  RegWriteA32D16(mesyreg::multi_event, 0b1);
  // mesyreg::max_transfer_data only works for mesyreg::multi_event=0xb
  // RegWriteA32D16(mesyreg::max_transfer_data, fEventTrigger);

  fDataLenFormat = 4; //  // 0-1-2-3-4: D8-16-32-MBLT64-nofEv for VME read
  if(fDataLenFormat < 2 || fDataLenFormat > 4)
    er("TAmTDC32", "Initialize: abnormal data_len_format: %d", fDataLenFormat);
  RegWriteA32D16(mesyreg::data_len_format, fDataLenFormat);
  // store time stamp (ts) instead of event counter in event trailer
  RegWriteA32D16(mesyreg::marking_type, 0b01); // 00: ev cnter; 01: ts; 11: extd ts
  // multi-hit or just store the first hit
  RegWriteA32D16(mesyreg::first_hit, 0b00); // multi-hit for both banks
  // set nim busy output function
  // 0: as busy (fifo full or daq stopped);
  // 3: Cbus output; 8: data over buffer thre 0x6018; 9: ev over buffer thre 0x601E
  RegWriteA32D16(mesyreg::NIM_busy, 9); // nof ev > irq_ev_thre
  RegWriteA32D16(mesyreg::bank0_trig_source, 0b01); // use trig0
  // trig_select: 0-> NIM inputs, 1->ECL inputs, **default is 0**
  RegWriteA32D16(mesyreg::trig_select, 0); // use trig from NIM inputs

  const auto &c = fConfig; // just for convenience
  if(c["bankThre"]) for(int i = 0; i < 2; i++)
    SetThreshold(i, c["bankThre"][i].as<uint16_t>());
  // set trigger latency and match window
  if(c["windowOffset"]){
    RegWriteA32D16(mesyreg::bank0_win_start,
      c["windowOffset"][0].as<short>() + 16384);
    RegWriteA32D16(mesyreg::bank1_win_start,
      c["windowOffset"][1].as<short>() + 16384);
  } // end if
  if(c["matchWindow"]){
    RegWriteA32D16(mesyreg::bank0_win_width, c["matchWindow"][0].as<int>());
    RegWriteA32D16(mesyreg::bank1_win_width, c["matchWindow"][1].as<int>());
  } // end if
  if(c["fallingEdge"])
    UseFallingEdge(c["fallingEdge"][0].as<bool>(), c["fallingEdge"][1].as<bool>());
} // end member function Initialize

// 0x0(default) ~ 0xff, in step of 1.5V/256
void TAmTDC32::SetThreshold(int bank, uint16_t thre) const{
  if(0 == bank) return RegWriteA32D16(mesyreg::bank0_input_thre, thre & 0xff);
  if(1 == bank) return RegWriteA32D16(mesyreg::bank1_input_thre, thre & 0xff);
  er("TAmTDC32", "SetThreshold:"
      "%s in slot %d, illegal bank id: %d", fCardName.data(), fSlot, bank);
} // end member function SetThreshold

void TAmTDC32::SetResolution(uint16_t res){
  if(res < 2 || res > 9)
    er("TAmTDC32", "SetResolution: illegal resolution: %d", res);
  RegWriteA32D16(mesyreg::resolution, res);
  fBlip = kRes[res];
} // end member function SetResolution

// opt0(1); whether using trailing edge for timing or not for bank0(1)
void TAmTDC32::UseFallingEdge(bool opt0, bool opt1){
  RegWriteA32D16(mesyreg::negative_edge, (opt1<<1) | opt0);
} // end member function UseFallingEdge

void TAmTDC32::Print() const{
  printf("\n\033[36;1m------ slot %d: %s ------\033[0m\n", fSlot, fCardName.data());
  printf("baseaddr: 0x%08x, firmware version: %d\n", fBaseAddress, fFirmWareRev);
  cout << std::boolalpha;
  static const char *bop[3] = {"connected", "independent", "zero dead time"};
  cout << "Bank Operation: \e[32;1m" << bop[fBankOp] << "\e[0m" << endl;
  if(fIRQLevel){
    cout << "\033[1mIRQ Level: " << fIRQLevel << ",\t IRQ Vector: ";
    cout << std::hex << fIRQVector << std::dec;
    cout << ",\t Event Trigger: " << fEventTrigger << "\033[0m" << endl;
  } // end if
  const int ut = RegReadA32D16(mesyreg::negative_edge);
  cout << "Use Trailing Edge (bank0-1): " << bool(ut & 0b01) << ", ";
  cout << bool(ut & 0b10) << endl;
  cout << "resolution: \033[32;1m" << fBlip*1000 << "ps\033[0m" << endl;
  cout << std::noboolalpha;

  const int windowOffset[2] = {
    RegReadA32D16(mesyreg::bank0_win_start) - 16384,
    RegReadA32D16(mesyreg::bank1_win_start) - 16384
  };
  const int matchWindow[2] = {
    RegReadA32D16(mesyreg::bank0_win_width),
    RegReadA32D16(mesyreg::bank1_win_width)
  };
  const double bankThre[2] = {
    RegReadA32D16(mesyreg::bank0_input_thre) * kThreStep,
    RegReadA32D16(mesyreg::bank1_input_thre) * kThreStep
  };
  printf("threshold (bank0-1): %.3f, %0.3f mV\n", bankThre[0], bankThre[1]);
  printf("window offset (bank0-1): %d, %d ns\n", windowOffset[0], windowOffset[1]);
  printf("match window (bank0-1): %d, %d ns\n", matchWindow[0], matchWindow[1]);

  const auto negedge = RegReadA32D16(mesyreg::negative_edge);
  cout << "negative_edge: " << (negedge & 0x1) << " " << (negedge>>1 & 0x1) << endl;
} // end member function Print

void TAmTDC32::PrintEvent(const uint32_t *ev){
  printf("\n\033[36;1m------ slot %d: %s ------\033[0m\n", fSlot, fCardName.data());
  cout << "\033[32;1m-------- Event Print -------- \033[0m" << endl;
  
  // the header has been MODIFIED to be compatible with CAEN decoding codes
  // and distinguishable from V1x90 cards
  // see TAMesytec::ModifyHeader(uint32_t &h) for details
  const uint32_t header = ev[0];
  const int nw = (header>>8 & 0xFFF); // nof following words
  const int res = header>>20 & 0xF;
  int sig = header>>24 & 0x3;
  const int slot = header>>27 & 0x1F;

  int extdts = 0; // extended time stamp
  static const char *pos[] = {"data", "header", "UDF", "trailer", "err",
    "err", "err", "err", "err", "err", "err"};
  printf("%s: slot: %d, ev_len: %d, res: %d->%fps\n",
    pos[sig], slot, nw + 1, res, fBlip*1000.);
  TAMsg::Print32(ev[1]); // DEBUG
  for(int i = 1; i < nw; i++){
    const uint32_t data = ev[i];
    const int sig = data>>30 & 0x3;
    const int subheader = data>>22 & 0xFF;
    const bool trig = data>>21 & 0x1;
    const int chid = data>>16 & 0x1F;
    const int tdc = data & 0xFFFF;
    const double tt = tdc * fBlip;
    if(!sig){
      if(!subheader) printf("dummy word\n");
      else if(0b10010 == subheader){
        extdts = data & 0xFFFF;
        printf("extended time stamp: %d\n", extdts);
      } // end if
      else if(0b10000 == subheader)
        printf("%s, trig: %d, chid: %02d, tdc: %d, time: %.6f ns\n",
          pos[sig], trig, chid, tdc, tt);
      else wn("TAmTDC32", "PrintEvent: abnormal subheader");
    } // end if
    else{
      printf("abnormal event word, i: %d\n", i);
      printf("%s, trig: %d, chid: %02d, tdc: %d, time: %.6fns\n",
        pos[sig], trig, chid, tdc, tt);
      wn("TAmTDC32", "PrintEvent: 0. abnormal event word encountered");
    } // end else
  } // end for over i
  const uint32_t trailer = ev[nw];
  sig = trailer>>30 & 0x3;
  if(3 != sig)
    wn("TAmADC32", "PrintEvent: Not a trailer word encountered, esig: %d", sig);
  else{
    const int ts = trailer & 0x3FFFFFFF;
    const double times = (ts + extdts*pow(2,30)) * 6.25e-8; // in seconds 62.5=1/16MHz
    printf("%s, time stamp: %u, extended time stamp: %u\n",
      pos[trailer>>30 & 0x3], ts, extdts);
    printf("time stamp in seconds: %.6f s (using 16MHz VME CLK)\n", times);
    fflush(stdout);
  } // end else
  cout << "\033[32;1m------- Event Print BYE ------- \033[0m" << endl;
} // end member function PrintEvent
